RAG 概念
什么是 RAG
RAG 是一种结合信息检索技术和 AI 内容生成的混合结构,可以解决大模型的知识时效性限制和幻读问题
简单来说就是让模型开卷考,让 AI 回答问题之前先查一下特定的知识库来获取信息,确保回答是基于资料而不是凭空捏造
RAG 和传统 AI 模型的区别
| 特性 |
传统大语言模型 |
RAG 增强模型 |
| 知识时效性 |
受训练数据截止日期限制 |
可接入最新知识库 |
| 领域专业性 |
泛化知识,专业深度有限 |
可接入专业领域知识 |
| 响应准确性 |
可能产生 “幻觉” |
基于检索的事实依据 |
| 可控性 |
依赖原始训练 |
可通过知识库定制输出 |
| 资源消耗 |
较高(需要大模型参数) |
模型可更小,结合外部知识 |
RAG 的工作流程
- 文档收集和切割
- 向量转换和存储
- 文档过滤和检索
- 检查增强和关联
文档收集和切割
文档收集:从各种来源(网页、PDF、数据库等)收集原始文档
文档预处理:清洗、标准化文本格式
文档切割:将长文档分割成适当大小的片段(俗称chunks)
- 基于固定大小(如1024个 token)
- 基于语义边界(如段落、文章)
- 基于递归分割策略(如递归字符 n-gram 切割)
向量转换和存储
向量转化:使用 Embedding 模型将文本转换为高纬度向量表示,可以捕获到文本的语义特征
向量存储:将转换的向量和文本存入向量数据库,支持高效相似性搜索
![[Pasted image 20251117195905.png]]
文档过滤和检索
查询处理:将用户的问题转换为向量表示
过滤机制:基于元数据、关键词或自定义规则进行过滤
相似度搜索:在向量数据库中查找与问题向量最相似的文档块,常用的相似度搜索算法有余弦相似度、欧式距离等
上下文组装:将检索出来的文档块组装成连贯的上下文
![[Pasted image 20251117200454.png]]
查询增强和关联
提示词组装:将检索到的相关文档和用户的问题组合成增强提示词
上下文融合:大模型基于增强提示生成模型
源引用:在回答中添加信息来源引用
后处理:格式化、摘要或其他处理以优化最终输出
RAG 的检索
RAG 的本质是 “有依据才回答,没依据坦白”, 所以检索失败的核心处理逻辑是:
先判断是不是真的没有答案:并不是说 “没找到” 就等于 “知识库没有”,可能是检索器没搜对(比如关键词匹配差,embedding 模型不合适)
再给出不编造的响应:明确告诉用户 “当前知识库没有相关内容”,并非强行生成
最后提供 “后续解决方案”: 引导用户补充信息、或者触发知识库更新,完成闭环
具体实行策略:
- 在给大模型的 Prompt 中明确加入规则,直接告诉用户 “无匹配信息”
1 2 3
| 规则1:仅基于提供的“检索到的知识库片段”回答问题; 规则2:如果检索到的片段为空、或没有任何与问题相关的信息,直接回复:“抱歉,当前知识库中没有找到与您的问题相关的有效信息,无法提供准确回答。您可以尝试补充问题细节,或咨询其他相关话题。”; 规则3:禁止在没有检索依据的情况下,编造任何来自“知识库”的信息。
|
1 2
| 规则4:如果检索失败(无相关知识库信息),可以基于你自身的通用知识简要回答,但必须在开头注明:“注意:以下回答来自模型的通用知识(非当前知识库),可能存在时效性或准确性问题,仅供参考:”; 规则5:如果通用知识也无法回答,直接执行规则2。
|
注意:这种方式会牺牲 “绝对时效性”(因为大模型的通用知识截止到训练 cutoff 时间),但能提升用户体验,适合对时效性要求不极端的场景(比如常识类问题)。
1
| 规则6:如果检索失败,且判断问题可能存在“信息模糊”(比如缺少关键条件、对象不明确),可以回复:“抱歉,当前知识库中没有找到相关信息。为了更准确地为您解答,建议补充以下细节:[举例说明需要补充的信息,如产品型号、时间范围、具体场景等],我会为您重新查询。”
|
工程优化细节:避免 “假检索失败”
- 设置检索阈值
- 检索器(如 FAISS、Milvus)返回的结果会有 “相关性分数”(比如余弦相似度),设置一个阈值(如 0.7):分数低于阈值,视为 “无相关信息”;分数高于阈值,才作为有效检索结果。
- 避免 “为了检索而检索”:比如返回一堆不相关的文档,导致模型基于错误依据回答。
- 优化检索策略
- 用「混合检索」(关键词检索 + 向量检索)
- 关键词扩展
- 调整 embedding 模型
RAG 使用
首先,我们要对自己准备好的知识库文档进行处理,然后保存到向量数据库中。这个过程俗称 ETL(抽取、转换、加载),Spring AI 提供了对 ETL 的支持,参考 官方文档。
ETL 的 3 大核心组件,按照顺序执行:
DocumentReader:读取文档,得到文档列表
DocumentTransformer:转换文档,得到处理后的文档列表
DocumentWriter:将文档列表保存到存储中(可以是向量数据库,也可以是其他存储)
导入依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-markdown-document-reader</artifactId> <version>1.0.0-M6</version> </dependency>
|
本地知识库
- 编写文档加载器类 DocumentLoader,负责读取所有 Markdown 文档并转换为 Document 列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Component @Slf4j public class DocumentLoader { private ResourcePatternResolver resourcePatternResolver; public LoveAppDocumentLoader(ResourcePatternResolver resourcePatternResolver){ this.resourcePatternResolver = resourcePatternResolver; } public List<Document> loadMarkdowns(){ List<Document> list = new ArrayList<>(); try { Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md"); log.info("找到 {} 个 Markdown 文档", resources.length); for (Resource resource : resources) { String filename = resource.getFilename(); log.info("正在加载文档:{}", filename); MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder() .withHorizontalRuleCreateDocument(true) .withIncludeCodeBlock(false) .withIncludeBlockquote(false) .withAdditionalMetadata("filename", filename) .build(); MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config); List<Document> docList = reader.get(); log.info("文档 {} 解析出 {} 个 Document", filename, docList.size()); list.addAll(docList); } log.info("最终加载 Document 总数:{}", list.size()); } catch (IOException e) { log.error("Markdown 文档加载失败", e); } return list; } }
|
- 新建 VectorStoreConfig 类,实现初始化向量数据库并且保存文档的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration public class VectorStoreConfig { @Resource private DocumentLoader DocumentLoader; @Bean VectorStore VectorStore(EmbeddingModel dashscopeEmbeddingModel){ SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel) .build(); List<Document> documents = DocumentLoader.loadMarkdowns(); simpleVectorStore.add(documents); return simpleVectorStore; } }
|
Spring AI 通过 Advisor 特性提供了开箱即用的 RAG 功能。主要是 QuestionAnswerAdvisor 问答拦截器和 RetrievalAugmentationAdvisor 检索增强拦截器,前者更简单易用、后者更灵活强大。
查询增强的原理其实很简单。向量数据库存储着 AI 模型本身不知道的数据,当用户问题发送给 AI 模型时,QuestionAnswerAdvisor 会查询向量数据库,获取与用户问题相关的文档。然后从向量数据库返回的响应会被附加到用户文本中,为 AI 模型提供上下文,帮助其生成回答。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Resource private VectorStore loveAppVectorStore;
public String doChatWithRag(String message, String chatId) { ChatResponse chatResponse = chatClient .prompt() .user(message) .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) .advisors(new MyLoggerAdvisor()) .advisors(new QuestionAnswerAdvisor(loveAppVectorStore)) .call() .chatResponse(); String content = chatResponse.getResult().getOutput().getText(); log.info("content: {}", content); return content; }
|
云知识库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
@Configuration @Slf4j public class LoveAppRagCloudAdvisorConfig { @Value("${spring.ai.dashscope.api-key}") private String dashScopeApiKey; @Bean public Advisor LoveAppRagCloudAdvisor(){ DashScopeApi dashScopeApi = new DashScopeApi(dashScopeApiKey); final String KNOWLEDGE_INDEX = "知识库名称"; DashScopeDocumentRetriever dashScopeDocumentRetriever = new DashScopeDocumentRetriever(dashScopeApi, DashScopeDocumentRetrieverOptions.builder() .withIndexName(KNOWLEDGE_INDEX) .build()); return RetrievalAugmentationAdvisor.builder() .documentRetriever(dashScopeDocumentRetriever) .build(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Resource private VectorStore loveAppVectorStore;
public String doChatWithRag(String message, String chatId){ ChatResponse chatResponse = chatClient .prompt() .user(message) .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) .advisors(new MyLoggerAdvisor())
.advisors(loveAppRagCloudAdvisor) .call() .chatResponse(); String text = chatResponse.getResult().getOutput().getText(); log.info("text: {}",text); return text; }
|